<?php
// ------------------------------------------------------------------------
// |@Author       : Jarmin <edshop@qq.com>
// |@----------------------------------------------------------------------
// |@Date         : 2023-06-25 14:29:56
// |@----------------------------------------------------------------------
// |@LastEditTime : 2023-12-20 07:56:07
// |@----------------------------------------------------------------------
// |@LastEditors  : Jarmin <jarmin@ladmin.cn>
// |@----------------------------------------------------------------------
// |@Description  : 
// |@----------------------------------------------------------------------
// |@FilePath     : File.php
// |@----------------------------------------------------------------------
// |@Copyright (c) 2023 http://www.ladmin.cn   All rights reserved. 
// ------------------------------------------------------------------------
namespace app\setting\logic;

use think\admin\Storage;
use think\admin\Logic;

/**
 * 文件逻辑类,用于实现文件相关业务逻辑操作
 */
class File extends Logic
{
    /**
     * 获取列表数据，包括文件夹和文件的信息。
     * 根据用户权限和分页参数获取对应的文件夹和文件。
     *
     * @param array $data 包含分页和其他查询参数
     * @return array 返回处理后的文件夹和文件数据
     */
    public function getList(array $data): array
    {
        // 构建查询条件，非管理员用户只能查询自己的数据
        $userId = $this->app->request->user_info['id'];
        $where = [];
        if (!empty($data['name'])) {
            $where[] = ['name', 'like', '%' . $data['name'] . '%'];
        }
        if (!empty($data['pid'])) {
            $where[] = ['pid', '=', $data['pid']];
        }
        if (empty($where)) {
            $where[] = ($userId != "1") ? ['user_id', '0', $userId] : ['id', '>', 0];
        }
        // 构建分页参数，默认为第1页，每页20条数据
        $page = $data['page'] ?? 1;
        $pageSize = $data['pageSize'] ?? 20;
        // 查询文件分类并设置默认属性
        $menus = $this->fetchSystemFolders($where, $pageSize, $page);
        // 查询文件并设置文件类型为 'file'
        $files = $this->modelSystemFile->where($where)->select()->toArray();
        $files = $this->setFileType($files);

        // 合并文件分类和文件数组，并转换成树形结构
        $menus['data'] = array_merge($menus['data'], $files);
        // 返回成功响应
        return $this->json(SUCCESS, lang('file.get_file_list_success'), $menus);
    }

    /**
     * 保存文件信息。如果 $data 中包含 id，则执行更新操作；否则执行添加操作。
     * @param array $data 文件分类信息。必须包含 name、sort、status 等字段。
     * @return JsonResponse
     */
    public function saveFile(array $data)
    {
        return $this->tryCatch(function () use ($data) {
            if (!empty($data['id'])) {
                // 如果传入的 $data 中有 id ，则执行更新操作
                $this->modelSystemFile->updateById($data['id'], $data);
            }
            // 返回保存结果和文件分类信息
            return $this->json(SUCCESS, lang('file.rename_success'), $data);
        });
    }

    /**
     * 保存文件分类信息。如果 $data 中包含 id，则执行更新操作；否则执行添加操作。
     * @param array $data 文件分类信息。必须包含 name、sort、status 等字段。
     * @return JsonResponse
     */
    public function upload(object $file, array $data)
    {
        if (!$file) return $this->json(ERROR, lang('file.please_select_file'));
        $this->getAllowExts();
        $data['name'] = $file->getoriginalName();
        $data['suffix'] = pathinfo($data['name'], PATHINFO_EXTENSION);
        $data['type']  = $file->getMime();
        $data['size'] = $file->getSize();
        $data['user_id'] = $this->app->request->user_info['id'];
        $fileName = time() + rand(1000, 9999) . $file->hash('sha1') . '.' . $file->extension();
        $path = $data['pid'] != 0 ? 'user_' . $data['user_id'] . '/' . $data['pid'] : 'user_' . $data['user_id'];
        $savename = \think\facade\Filesystem::disk('public')->putFileAs($path, $file, $fileName);
        $data['url'] = str_replace("\\", '/', $savename);
        return $this->json(SUCCESS, lang('file.upload_success'), $data);
    }

    /**
     * 获取允许上传的文件类型。
     * @return JsonResponse
     */
    public function getAllowExts()
    {
        $allows = $this->modelSystemConfig->where(['type' => 'storage', 'name' => 'allow_exts'])->value('value');
        $allows = str2arr($allows);
        foreach ($allows as $ext) $exts[] = Storage::mime($ext);
        return $this->json(SUCCESS, lang('file.get_allow_exts'), $exts);
    }

    /**
     * 从数据库获取文件夹数据，并应用分页。
     * 此函数还设置了文件夹的默认属性。
     *
     * @param array $where 查询条件
     * @param int $pageSize 每页显示的数量
     * @param int $page 当前页数
     * @return array 返回文件夹数据的数组表示
     */
    private function fetchSystemFolders($where, $pageSize, $page)
    {
        // 从数据库获取文件分类数据，并进行分页处理
        $menus = $this->modelSystemFolder->where($where)->order(['name' => 'asc'])->paginate(['list_rows' => $pageSize, 'page' => $page]);
        // 设置文件分类默认属性并返回数组格式的数据
        return $this->setFolderDefaults($menus->toArray());
    }

    /**
     * 设置文件夹的默认属性值。
     * 遍历文件夹数组，并为每个文件夹设置预定义的默认属性。
     *
     * @param array $folders 文件夹数据的数组，每个文件夹作为一个数组元素
     * @return array 返回包含已更新属性的文件夹数组
     */
    private function setFolderDefaults($folders)
    {
        // 遍历文件夹数组
        foreach ($folders['data'] as &$folder) {
            // 确保每个元素是数组
            if (is_array($folder)) {
                // 设置文件夹的类型为 'folder'
                $folder['type'] = 'folder';
                // 设置文件夹的其他属性为默认值
                $folder['size'] = '-';
                $folder['path'] = '-';
                $folder['dimension'] = '-';
                $folder['status'] = '-';
                $folder['quote_count'] = '-';
                $folder['visible'] = false;
            }
        }
        // 返回更新后的文件夹数组
        return $folders;
    }

    /**
     * 设置文件类型为 'file'。
     * 遍历文件数组，为每个文件对象设置 'type' 属性。
     *
     * @param array $files 文件对象数组
     * @return array 返回设置了类型的文件数组
     */
    private function setFileType($files)
    {
        // 遍历文件数组
        foreach ($files as &$file) {
            // 确保每个元素是数组
            if (is_array($file)) {
                // 设置文件的类型为 'file'
                $file['type'] = 'file';
                $file['visible'] = false;
            }
        }
        // 返回更新后的文件数组
        return $files;
    }

    /**
     * 以事务方式删除多个文件分类及其所有子孙文件分类。
     * @param array $data 待删除文件分类ID列表
     * @return JsonResponse
     * @throws \think\db\exception\PDOException
     * @throws \Throwable
     */
    public function deleteFile(array $data)
    {
        return $this->tryCatch(function () use ($data) {
            // 找到所有待删除的节点和它们的所有子节点。
            $toDelete = $this->modelSystemFile->whereIn('id', $data['ids'])->select();
            $toDeleteIds = array_column($toDelete->toArray(), 'id');
            foreach ($toDelete->toArray() as $item) {
                $toDeleteIds = array_merge($toDeleteIds, $this->getDescendantIds($this->modelSystemFile, $item['id']));
            }
            // 删除所有待删除的节点和它们的所有子节点。
            $this->modelSystemFile->deleteById($toDeleteIds);
            // 返回删除成功的信息和状态
            return $this->json(SUCCESS, lang('file.delete_file_success'));
        });
    }
}